/*
 * Copyright (C) 1997-2001 Id Software, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 * 
 * =======================================================================
 *
 * This file implements the entity and network protocol parsing
 *
 * =======================================================================
 */

#include "header/client.h"

void CL_DownloadFileName(char *dest, int destlen, char *fn);
void CL_ParseDownload (void);

int	bitcounts[32]; /* just for protocol profiling */ 

char *svc_strings[256] = {
	"svc_bad",

	"svc_muzzleflash",
	"svc_muzzlflash2",
	"svc_temp_entity",
	"svc_layout",
	"svc_inventory",

	"svc_nop",
	"svc_disconnect",
	"svc_reconnect",
	"svc_sound",
	"svc_print",
	"svc_stufftext",
	"svc_serverdata",
	"svc_configstring",
	"svc_spawnbaseline",
	"svc_centerprint",
	"svc_download",
	"svc_playerinfo",
	"svc_packetentities",
	"svc_deltapacketentities",
	"svc_frame"
};

void CL_RegisterSounds (void) {
	int		i;

	S_BeginRegistration ();
	CL_RegisterTEntSounds ();

	for (i=1 ; i<MAX_SOUNDS ; i++) {
		if (!cl.configstrings[CS_SOUNDS+i][0])
			break;

		cl.sound_precache[i] = S_RegisterSound (cl.configstrings[CS_SOUNDS+i]);
		Sys_SendKeyEvents ();
	}

	S_EndRegistration ();
}

/*
 * Returns the entity number and the header bits
 */
int CL_ParseEntityBits (unsigned *bits) {
	unsigned	b, total;
	int			i;
	int			number;

	total = MSG_ReadByte (&net_message);

	if (total & U_MOREBITS1) {
		b = MSG_ReadByte (&net_message);
		total |= b<<8;
	}

	if (total & U_MOREBITS2) {
		b = MSG_ReadByte (&net_message);
		total |= b<<16;
	}

	if (total & U_MOREBITS3) {
		b = MSG_ReadByte (&net_message);
		total |= b<<24;
	}

	/* count the bits for net profiling */
	for (i=0 ; i<32 ; i++)
		if (total&(1<<i))
			bitcounts[i]++;

	if (total & U_NUMBER16)
		number = MSG_ReadShort (&net_message);

	else
		number = MSG_ReadByte (&net_message);

	*bits = total;

	return number;
}

/*
 * Can go from either a baseline or a previous packet_entity
 */
void CL_ParseDelta (entity_state_t *from, entity_state_t *to, int number, int bits) {
	/* set everything to the state we are delta'ing from */
	*to = *from;

	VectorCopy (from->origin, to->old_origin);
	to->number = number;

	if (bits & U_MODEL)
		to->modelindex = MSG_ReadByte (&net_message);

	if (bits & U_MODEL2)
		to->modelindex2 = MSG_ReadByte (&net_message);

	if (bits & U_MODEL3)
		to->modelindex3 = MSG_ReadByte (&net_message);

	if (bits & U_MODEL4)
		to->modelindex4 = MSG_ReadByte (&net_message);

	if (bits & U_FRAME8)
		to->frame = MSG_ReadByte (&net_message);

	if (bits & U_FRAME16)
		to->frame = MSG_ReadShort (&net_message);

	/* used for laser colors */
	if ((bits & U_SKIN8) && (bits & U_SKIN16))
		to->skinnum = MSG_ReadLong(&net_message);

	else if (bits & U_SKIN8)
		to->skinnum = MSG_ReadByte(&net_message);

	else if (bits & U_SKIN16)
		to->skinnum = MSG_ReadShort(&net_message);

	if ( (bits & (U_EFFECTS8|U_EFFECTS16)) == (U_EFFECTS8|U_EFFECTS16) )
		to->effects = MSG_ReadLong(&net_message);

	else if (bits & U_EFFECTS8)
		to->effects = MSG_ReadByte(&net_message);

	else if (bits & U_EFFECTS16)
		to->effects = MSG_ReadShort(&net_message);

	if ( (bits & (U_RENDERFX8|U_RENDERFX16)) == (U_RENDERFX8|U_RENDERFX16) )
		to->renderfx = MSG_ReadLong(&net_message);

	else if (bits & U_RENDERFX8)
		to->renderfx = MSG_ReadByte(&net_message);

	else if (bits & U_RENDERFX16)
		to->renderfx = MSG_ReadShort(&net_message);

	if (bits & U_ORIGIN1)
		to->origin[0] = MSG_ReadCoord (&net_message);

	if (bits & U_ORIGIN2)
		to->origin[1] = MSG_ReadCoord (&net_message);

	if (bits & U_ORIGIN3)
		to->origin[2] = MSG_ReadCoord (&net_message);

	if (bits & U_ANGLE1)
		to->angles[0] = MSG_ReadAngle(&net_message);

	if (bits & U_ANGLE2)
		to->angles[1] = MSG_ReadAngle(&net_message);

	if (bits & U_ANGLE3)
		to->angles[2] = MSG_ReadAngle(&net_message);

	if (bits & U_OLDORIGIN)
		MSG_ReadPos (&net_message, to->old_origin);

	if (bits & U_SOUND)
		to->sound = MSG_ReadByte (&net_message);

	if (bits & U_EVENT)
		to->event = MSG_ReadByte (&net_message);

	else
		to->event = 0;

	if (bits & U_SOLID)
		to->solid = MSG_ReadShort (&net_message);
}

/*
 * Parses deltas from the given base and adds the resulting entity to
 * the current frame
 */
void CL_DeltaEntity (frame_t *frame, int newnum, entity_state_t *old,
                     int bits) {
	centity_t		*ent;
	entity_state_t	*state;

	ent = &cl_entities[newnum];

	state = &cl_parse_entities[cl.parse_entities & (MAX_PARSE_ENTITIES-1)];
	cl.parse_entities++;
	frame->num_entities++;

	CL_ParseDelta (old, state, newnum, bits);

	/* some data changes will force no lerping */
	if (state->modelindex != ent->current.modelindex
	        || state->modelindex2 != ent->current.modelindex2
	        || state->modelindex3 != ent->current.modelindex3
	        || state->modelindex4 != ent->current.modelindex4
	        || state->event == EV_PLAYER_TELEPORT
	        || state->event == EV_OTHER_TELEPORT
	        || abs((int)(state->origin[0] - ent->current.origin[0])) > 512
	        || abs((int)(state->origin[1] - ent->current.origin[1])) > 512
	        || abs((int)(state->origin[2] - ent->current.origin[2])) > 512
	   ) {
		ent->serverframe = -99;
	}
	/* wasn't in last update, so initialize some things */
	if (ent->serverframe != cl.frame.serverframe - 1) {
		ent->trailcount = 1024; /* for diminishing rocket / grenade trails */
		
		/* duplicate the current state so 
		 * lerping doesn't hurt anything */
		ent->prev = *state;

		if (state->event == EV_OTHER_TELEPORT) {
			VectorCopy (state->origin, ent->prev.origin);
			VectorCopy (state->origin, ent->lerp_origin);

		} else {
			VectorCopy (state->old_origin, ent->prev.origin);
			VectorCopy (state->old_origin, ent->lerp_origin);
		}

	} else {
		/* shuffle the last state to previous */
		ent->prev = ent->current;
	}

	ent->serverframe = cl.frame.serverframe;
	ent->current = *state;
}

/*
 * An svc_packetentities has just been parsed, deal with the rest of the
 * data stream.
 */
void CL_ParsePacketEntities (frame_t *oldframe, frame_t *newframe) {
	unsigned int	newnum;
	unsigned		bits;
	entity_state_t
	*oldstate = NULL;
	int			oldindex, oldnum;

	newframe->parse_entities = cl.parse_entities;
	newframe->num_entities = 0;

	/* delta from the entities present in oldframe */
	oldindex = 0;

	if (!oldframe)
		oldnum = 99999;

	else {
		if (oldindex >= oldframe->num_entities)
			oldnum = 99999;

		else {
			oldstate = &cl_parse_entities[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)];
			oldnum = oldstate->number;
		}
	}

	while (1) {
		newnum = CL_ParseEntityBits (&bits);

		if (newnum >= MAX_EDICTS)
			Com_Error (ERR_DROP,"CL_ParsePacketEntities: bad number:%i", newnum);

		if (net_message.readcount > net_message.cursize)
			Com_Error (ERR_DROP,"CL_ParsePacketEntities: end of message");

		if (!newnum)
			break;

		while (oldnum < newnum) {
			/* one or more entities from the old packet are unchanged */
			if (cl_shownet->value == 3)
				Com_Printf ("   unchanged: %i\n", oldnum);

			CL_DeltaEntity (newframe, oldnum, oldstate, 0);

			oldindex++;

			if (oldindex >= oldframe->num_entities)
				oldnum = 99999;

			else {
				oldstate = &cl_parse_entities[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)];
				oldnum = oldstate->number;
			}
		}

		if (bits & U_REMOVE) {
			/* the entity present in oldframe is not in the current frame */
			if (cl_shownet->value == 3)
				Com_Printf ("   remove: %i\n", newnum);

			if (oldnum != newnum)
				Com_Printf ("U_REMOVE: oldnum != newnum\n");

			oldindex++;

			if (oldindex >= oldframe->num_entities)
				oldnum = 99999;

			else {
				oldstate = &cl_parse_entities[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)];
				oldnum = oldstate->number;
			}

			continue;
		}

		if (oldnum == newnum) {
			/* delta from previous state */
			if (cl_shownet->value == 3)
				Com_Printf ("   delta: %i\n", newnum);

			CL_DeltaEntity (newframe, newnum, oldstate, bits);

			oldindex++;

			if (oldindex >= oldframe->num_entities)
				oldnum = 99999;

			else {
				oldstate = &cl_parse_entities[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)];
				oldnum = oldstate->number;
			}

			continue;
		}

		if (oldnum > newnum) {
			/* delta from baseline */
			if (cl_shownet->value == 3)
				Com_Printf ("   baseline: %i\n", newnum);

			CL_DeltaEntity (newframe, newnum, &cl_entities[newnum].baseline, bits);
			continue;
		}

	}

	/* any remaining entities in the old frame are copied over */
	while (oldnum != 99999) {
		/* one or more entities from the old packet are unchanged */
		if (cl_shownet->value == 3)
			Com_Printf ("   unchanged: %i\n", oldnum);

		CL_DeltaEntity (newframe, oldnum, oldstate, 0);

		oldindex++;

		if (oldindex >= oldframe->num_entities)
			oldnum = 99999;

		else {
			oldstate = &cl_parse_entities[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)];
			oldnum = oldstate->number;
		}
	}
}

void CL_ParsePlayerstate (frame_t *oldframe, frame_t *newframe) {
	int			flags;
	player_state_t	*state;
	int			i;
	int			statbits;

	state = &newframe->playerstate;

	/* clear to old value before delta parsing */
	if (oldframe)
		*state = oldframe->playerstate;

	else
		memset (state, 0, sizeof(*state));

	flags = MSG_ReadShort (&net_message);

	/* parse the pmove_state_t */
	if (flags & PS_M_TYPE)
		state->pmove.pm_type = MSG_ReadByte (&net_message);

	if (flags & PS_M_ORIGIN) {
		state->pmove.origin[0] = MSG_ReadShort (&net_message);
		state->pmove.origin[1] = MSG_ReadShort (&net_message);
		state->pmove.origin[2] = MSG_ReadShort (&net_message);
	}

	if (flags & PS_M_VELOCITY) {
		state->pmove.velocity[0] = MSG_ReadShort (&net_message);
		state->pmove.velocity[1] = MSG_ReadShort (&net_message);
		state->pmove.velocity[2] = MSG_ReadShort (&net_message);
	}

	if (flags & PS_M_TIME)
		state->pmove.pm_time = MSG_ReadByte (&net_message);

	if (flags & PS_M_FLAGS)
		state->pmove.pm_flags = MSG_ReadByte (&net_message);

	if (flags & PS_M_GRAVITY)
		state->pmove.gravity = MSG_ReadShort (&net_message);

	if (flags & PS_M_DELTA_ANGLES) {
		state->pmove.delta_angles[0] = MSG_ReadShort (&net_message);
		state->pmove.delta_angles[1] = MSG_ReadShort (&net_message);
		state->pmove.delta_angles[2] = MSG_ReadShort (&net_message);
	}

	if (cl.attractloop)
		state->pmove.pm_type = PM_FREEZE; /* demo playback */

	/* parse the rest of the player_state_t */
	if (flags & PS_VIEWOFFSET) {
		state->viewoffset[0] = MSG_ReadChar (&net_message) * 0.25f;
		state->viewoffset[1] = MSG_ReadChar (&net_message) * 0.25f;
		state->viewoffset[2] = MSG_ReadChar (&net_message) * 0.25f;
	}

	if (flags & PS_VIEWANGLES) {
		state->viewangles[0] = MSG_ReadAngle16 (&net_message);
		state->viewangles[1] = MSG_ReadAngle16 (&net_message);
		state->viewangles[2] = MSG_ReadAngle16 (&net_message);
	}

	if (flags & PS_KICKANGLES) {
		state->kick_angles[0] = MSG_ReadChar (&net_message) * 0.25f;
		state->kick_angles[1] = MSG_ReadChar (&net_message) * 0.25f;
		state->kick_angles[2] = MSG_ReadChar (&net_message) * 0.25f;
	}

	if (flags & PS_WEAPONINDEX) {
		state->gunindex = MSG_ReadByte (&net_message);
	}

	if (flags & PS_WEAPONFRAME) {
		state->gunframe = MSG_ReadByte (&net_message);
		state->gunoffset[0] = MSG_ReadChar (&net_message)*0.25f;
		state->gunoffset[1] = MSG_ReadChar (&net_message)*0.25f;
		state->gunoffset[2] = MSG_ReadChar (&net_message)*0.25f;
		state->gunangles[0] = MSG_ReadChar (&net_message)*0.25f;
		state->gunangles[1] = MSG_ReadChar (&net_message)*0.25f;
		state->gunangles[2] = MSG_ReadChar (&net_message)*0.25f;
	}

	if (flags & PS_BLEND) {
		state->blend[0] = MSG_ReadByte (&net_message)/255.0f;
		state->blend[1] = MSG_ReadByte (&net_message)/255.0f;
		state->blend[2] = MSG_ReadByte (&net_message)/255.0f;
		state->blend[3] = MSG_ReadByte (&net_message)/255.0f;
	}

	if (flags & PS_FOV)
		state->fov = (float)MSG_ReadByte (&net_message);

	if (flags & PS_RDFLAGS)
		state->rdflags = MSG_ReadByte (&net_message);

	/* parse stats */
	statbits = MSG_ReadLong (&net_message);

	for (i=0 ; i<MAX_STATS ; i++)
		if (statbits & (1<<i) )
			state->stats[i] = MSG_ReadShort(&net_message);
}

void CL_FireEntityEvents (frame_t *frame) {
	entity_state_t		*s1;
	int					pnum, num;

	for (pnum = 0 ; pnum<frame->num_entities ; pnum++) {
		num = (frame->parse_entities + pnum)&(MAX_PARSE_ENTITIES-1);
		s1 = &cl_parse_entities[num];

		if (s1->event)
			CL_EntityEvent (s1);

		if (s1->effects & EF_TELEPORTER)
			CL_TeleporterParticles (s1);
	}
}

void CL_ParseFrame (void) {
	int			cmd;
	int			len;
	frame_t		*old;

	memset (&cl.frame, 0, sizeof(cl.frame));

	cl.frame.serverframe = MSG_ReadLong (&net_message);
	cl.frame.deltaframe = MSG_ReadLong (&net_message);
	cl.frame.servertime = cl.frame.serverframe*100;

	/* BIG HACK to let old demos continue to work */
	if (cls.serverProtocol != 26)
		cl.surpressCount = MSG_ReadByte (&net_message);

	if (cl_shownet->value == 3)
		Com_Printf ("   frame:%i  delta:%i\n", cl.frame.serverframe,
		            cl.frame.deltaframe);

	/*
	 * If the frame is delta compressed from data that we
	 * no longer have available, we must suck up the rest of
	 * the frame, but not use it, then ask for a non-compressed
	 * message
	 */
	if (cl.frame.deltaframe <= 0) {
		cl.frame.valid = true; /* uncompressed frame */
		old = NULL;
		cls.demowaiting = false; /* we can start recording now */

	} else {
		old = &cl.frames[cl.frame.deltaframe & UPDATE_MASK];

		if (!old->valid) {
			/* should never happen */
			Com_Printf ("Delta from invalid frame (not supposed to happen!).\n");
		}

		if (old->serverframe != cl.frame.deltaframe) {
			/* The frame that the server did the delta from
			 * is too old, so we can't reconstruct it properly. */
			Com_Printf ("Delta frame too old.\n");

		} else if (cl.parse_entities - old->parse_entities > MAX_PARSE_ENTITIES-128) {
			Com_Printf ("Delta parse_entities too old.\n");

		} else
			cl.frame.valid = true; /* valid delta parse */
	}

	/* clamp time */
	if (cl.time > cl.frame.servertime)
		cl.time = cl.frame.servertime;

	else if (cl.time < cl.frame.servertime - 100)
		cl.time = cl.frame.servertime - 100;

	/* read areabits */
	len = MSG_ReadByte (&net_message);
	MSG_ReadData (&net_message, &cl.frame.areabits, len);

	/* read playerinfo */
	cmd = MSG_ReadByte (&net_message);
	SHOWNET(svc_strings[cmd]);

	if (cmd != svc_playerinfo)
		Com_Error (ERR_DROP, "CL_ParseFrame: 0x%X not playerinfo", cmd);

	CL_ParsePlayerstate (old, &cl.frame);

	/* read packet entities */
	cmd = MSG_ReadByte (&net_message);
	SHOWNET(svc_strings[cmd]);

	if (cmd != svc_packetentities)
		Com_Error (ERR_DROP, "CL_ParseFrame: 0x%X not packetentities", cmd);

	CL_ParsePacketEntities (old, &cl.frame);

	/* save the frame off in the backup array for later delta comparisons */
	cl.frames[cl.frame.serverframe & UPDATE_MASK] = cl.frame;

	if (cl.frame.valid) {
		/* getting a valid frame message ends the connection process */
		if (cls.state != ca_active) {
			cls.state = ca_active;
			cl.force_refdef = true;
			cl.predicted_origin[0] = cl.frame.playerstate.pmove.origin[0]*0.125f;
			cl.predicted_origin[1] = cl.frame.playerstate.pmove.origin[1]*0.125f;
			cl.predicted_origin[2] = cl.frame.playerstate.pmove.origin[2]*0.125f;
			VectorCopy (cl.frame.playerstate.viewangles, cl.predicted_angles);

			if (cls.disable_servercount != cl.servercount
			        && cl.refresh_prepped)
				SCR_EndLoadingPlaque (); /* get rid of loading plaque */

			cl.sound_prepped = true;
		}

		/* fire entity events */
		CL_FireEntityEvents (&cl.frame);

		if (!(!cl_predict->value || (cl.frame.playerstate.pmove.pm_flags & PMF_NO_PREDICTION)))
			CL_CheckPredictionError();
	}
} 

void CL_ParseServerData (void) {
	extern cvar_t	*fs_gamedirvar;
	char	*str;
	int		i;

	Com_DPrintf ("Serverdata packet received.\n");

	/* wipe the client_state_t struct */
	CL_ClearState ();
	cls.state = ca_connected;

	/* parse protocol version number */
	i = MSG_ReadLong (&net_message);
	cls.serverProtocol = i;

	/* another demo hack */
	if (Com_ServerState() && PROTOCOL_VERSION == 34) {
	} else if (i != PROTOCOL_VERSION)
		Com_Error (ERR_DROP,"Server returned version %i, not %i", i, PROTOCOL_VERSION);

	cl.servercount = MSG_ReadLong (&net_message);
	cl.attractloop = MSG_ReadByte (&net_message);

	/* game directory */
	str = MSG_ReadString (&net_message);
	strncpy (cl.gamedir, str, sizeof(cl.gamedir)-1);

	/* set gamedir */
	if ((*str && (!fs_gamedirvar->string || !*fs_gamedirvar->string || strcmp(fs_gamedirvar->string, str))) || (!*str && (fs_gamedirvar->string || *fs_gamedirvar->string)))
		Cvar_Set("game", str);

	/* parse player entity number */
	cl.playernum = MSG_ReadShort (&net_message);

	/* get the full level name */
	str = MSG_ReadString (&net_message);

	if (cl.playernum == -1) {
		/* playing a cinematic or showing a pic, not a level */
		SCR_PlayCinematic (str);

	} else {
		/* seperate the printfs so the server
		   message can have a color */
		Com_Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n");
		Com_Printf ("%c%s\n", 2, str);

		/* need to prep refresh at next oportunity */
		cl.refresh_prepped = false;
	}
}

void CL_ParseBaseline (void) {
	entity_state_t	*es;
	unsigned			bits;
	int				newnum;
	entity_state_t	nullstate;

	memset (&nullstate, 0, sizeof(nullstate));

	newnum = CL_ParseEntityBits (&bits);
	es = &cl_entities[newnum].baseline;
	CL_ParseDelta (&nullstate, es, newnum, bits);
}

void CL_LoadClientinfo (clientinfo_t *ci, char *s) {
	int i;
	char		*t;
	char		model_name[MAX_QPATH];
	char		skin_name[MAX_QPATH];
	char		model_filename[MAX_QPATH];
	char		skin_filename[MAX_QPATH];
	char		weapon_filename[MAX_QPATH];

	strncpy(ci->cinfo, s, sizeof(ci->cinfo));
	ci->cinfo[sizeof(ci->cinfo)-1] = 0;

	/* isolate the player's name */
	strncpy(ci->name, s, sizeof(ci->name));
	ci->name[sizeof(ci->name)-1] = 0;
	t = strstr (s, "\\");

	if (t) {
		ci->name[t-s] = 0;
		s = t+1;
	}

	if (cl_noskins->value || *s == 0) {
		strcpy (model_filename, "players/male/tris.md2");
		strcpy (weapon_filename, "players/male/weapon.md2");
		strcpy (skin_filename, "players/male/grunt.pcx");
		strcpy (ci->iconname, "/players/male/grunt_i.pcx");
		ci->model = re.RegisterModel (model_filename);
		memset(ci->weaponmodel, 0, sizeof(ci->weaponmodel));
		ci->weaponmodel[0] = re.RegisterModel (weapon_filename);
		ci->skin = re.RegisterSkin (skin_filename);
		ci->icon = re.RegisterPic (ci->iconname);

	} else {
		/* isolate the model name */
		strcpy (model_name, s);
		t = strstr(model_name, "/");

		if (!t)
			t = strstr(model_name, "\\");

		if (!t)
			t = model_name;

		*t = 0;

		/* isolate the skin name */
		strcpy (skin_name, s + strlen(model_name) + 1);

		/* model file */
		Com_sprintf (model_filename, sizeof(model_filename), "players/%s/tris.md2", model_name);
		ci->model = re.RegisterModel (model_filename);

		if (!ci->model) {
			strcpy(model_name, "male");
			Com_sprintf (model_filename, sizeof(model_filename), "players/male/tris.md2");
			ci->model = re.RegisterModel (model_filename);
		}

		/* skin file */
		Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/%s.pcx", model_name, skin_name);
		ci->skin = re.RegisterSkin (skin_filename);

		/* if we don't have the skin and the model wasn't male,
		   see if the male has it (this is for CTF's skins) */
		if (!ci->skin && Q_stricmp(model_name, "male")) {
			/* change model to male */
			strcpy(model_name, "male");
			Com_sprintf (model_filename, sizeof(model_filename), "players/male/tris.md2");
			ci->model = re.RegisterModel (model_filename);

			/* see if the skin exists for the male model */
			Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/%s.pcx", model_name, skin_name);
			ci->skin = re.RegisterSkin (skin_filename);
		}

		/* if we still don't have a skin, it means that the male model didn't have
		   it, so default to grunt */
		if (!ci->skin) {
			/* see if the skin exists for the male model */
			Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/grunt.pcx", model_name, skin_name);
			ci->skin = re.RegisterSkin (skin_filename);
		}

		/* weapon file */
		for (i = 0; i < num_cl_weaponmodels; i++) {
			Com_sprintf (weapon_filename, sizeof(weapon_filename), "players/%s/%s", model_name, cl_weaponmodels[i]);
			ci->weaponmodel[i] = re.RegisterModel(weapon_filename);

			if (!ci->weaponmodel[i] && strcmp(model_name, "cyborg") == 0) {
				/* try male */
				Com_sprintf (weapon_filename, sizeof(weapon_filename), "players/male/%s", cl_weaponmodels[i]);
				ci->weaponmodel[i] = re.RegisterModel(weapon_filename);
			}

			if (!cl_vwep->value)
				break; /* only one when vwep is off */
		}

		/* icon file */
		Com_sprintf (ci->iconname, sizeof(ci->iconname), "/players/%s/%s_i.pcx", model_name, skin_name);
		ci->icon = re.RegisterPic (ci->iconname);
	}

	/* must have loaded all data types to be valid */
	if (!ci->skin || !ci->icon || !ci->model || !ci->weaponmodel[0]) {
		ci->skin = NULL;
		ci->icon = NULL;
		ci->model = NULL;
		ci->weaponmodel[0] = NULL;
		return;
	}
}

/*
 * Load the skin, icon, and model for a client
 */
void CL_ParseClientinfo (int player) {
	char			*s;
	clientinfo_t	*ci;

	s = cl.configstrings[player+CS_PLAYERSKINS];

	ci = &cl.clientinfo[player];

	CL_LoadClientinfo (ci, s);
}

void CL_ParseConfigString (void) {
	int		i;
	char	*s;
	char	olds[MAX_QPATH];

	i = MSG_ReadShort (&net_message);

	if (i < 0 || i >= MAX_CONFIGSTRINGS)
		Com_Error (ERR_DROP, "configstring > MAX_CONFIGSTRINGS");

	s = MSG_ReadString(&net_message);

	strncpy (olds, cl.configstrings[i], sizeof(olds));
	olds[sizeof(olds) - 1] = 0;

	strcpy (cl.configstrings[i], s);

	/* do something apropriate */
	if (i >= CS_LIGHTS && i < CS_LIGHTS+MAX_LIGHTSTYLES)
		CL_SetLightstyle (i - CS_LIGHTS);

	else if (i == CS_CDTRACK) {
		if (cl.refresh_prepped) {
			CDAudio_Play (atoi(cl.configstrings[CS_CDTRACK]), true);

			/* OGG/Vorbis */
			if (atoi(cl.configstrings[CS_CDTRACK]) < 10) {
				char tmp[3] = "0";
				OGG_ParseCmd(strcat(tmp, cl.configstrings[CS_CDTRACK]));

			} else {
				OGG_ParseCmd(cl.configstrings[CS_CDTRACK]);
			}
		}

	} else if (i >= CS_MODELS && i < CS_MODELS+MAX_MODELS) {
		if (cl.refresh_prepped) {
			cl.model_draw[i-CS_MODELS] = re.RegisterModel (cl.configstrings[i]);

			if (cl.configstrings[i][0] == '*')
				cl.model_clip[i-CS_MODELS] = CM_InlineModel (cl.configstrings[i]);

			else
				cl.model_clip[i-CS_MODELS] = NULL;
		}

	} else if (i >= CS_SOUNDS && i < CS_SOUNDS+MAX_MODELS) {
		if (cl.refresh_prepped)
			cl.sound_precache[i-CS_SOUNDS] = S_RegisterSound (cl.configstrings[i]);

	} else if (i >= CS_IMAGES && i < CS_IMAGES+MAX_MODELS) {
		if (cl.refresh_prepped)
			cl.image_precache[i-CS_IMAGES] = re.RegisterPic (cl.configstrings[i]);

	} else if (i >= CS_PLAYERSKINS && i < CS_PLAYERSKINS+MAX_CLIENTS) {
		if (cl.refresh_prepped && strcmp(olds, s))
			CL_ParseClientinfo (i-CS_PLAYERSKINS);
	}
}

void CL_ParseStartSoundPacket(void) {
	vec3_t  pos_v;
	float	*pos;
	int 	channel, ent;
	int 	sound_num;
	float 	volume;
	float 	attenuation;
	int		flags;
	float	ofs;

	flags = MSG_ReadByte (&net_message);
	sound_num = MSG_ReadByte (&net_message);

	if (flags & SND_VOLUME)
		volume = MSG_ReadByte (&net_message) / 255.0f;

	else
		volume = DEFAULT_SOUND_PACKET_VOLUME;

	if (flags & SND_ATTENUATION)
		attenuation = MSG_ReadByte (&net_message) / 64.0f;

	else
		attenuation = DEFAULT_SOUND_PACKET_ATTENUATION;

	if (flags & SND_OFFSET)
		ofs = MSG_ReadByte (&net_message) / 1000.0f;

	else
		ofs = 0;

	if (flags & SND_ENT) {
		/* entity reletive */
		channel = MSG_ReadShort(&net_message);
		ent = channel>>3;

		if (ent > MAX_EDICTS)
			Com_Error (ERR_DROP,"CL_ParseStartSoundPacket: ent = %i", ent);

		channel &= 7;

	} else {
		ent = 0;
		channel = 0;
	}

	if (flags & SND_POS) {
		/* positioned in space */
		MSG_ReadPos (&net_message, pos_v);

		pos = pos_v;

	} else
		/* use entity number */
		pos = NULL;

	if (!cl.sound_precache[sound_num])
		return;

	S_StartSound (pos, ent, channel, cl.sound_precache[sound_num], volume, attenuation, ofs);
}

void SHOWNET(char *s) {
	if (cl_shownet->value>=2)
		Com_Printf ("%3i:%s\n", net_message.readcount-1, s);
}

void CL_ParseServerMessage (void) {
	int		cmd;
	char	*s;
	int		i;

	/* if recording demos, copy the message out */
	if (cl_shownet->value == 1)
		Com_Printf ("%i ",net_message.cursize);

	else if (cl_shownet->value >= 2)
		Com_Printf ("------------------\n");

	/* parse the message */
	while (1) {
		if (net_message.readcount > net_message.cursize) {
			Com_Error (ERR_DROP,"CL_ParseServerMessage: Bad server message");
			break;
		}

		cmd = MSG_ReadByte (&net_message);

		if (cmd == -1) {
			SHOWNET("END OF MESSAGE");
			break;
		}

		if (cl_shownet->value>=2) {
			if (!svc_strings[cmd])
				Com_Printf ("%3i:BAD CMD %i\n", net_message.readcount-1,cmd);

			else
				SHOWNET(svc_strings[cmd]);
		}

		/* other commands */
		switch (cmd) {
			default:
				Com_Error (ERR_DROP,"CL_ParseServerMessage: Illegible server message\n");
				break;

			case svc_nop:
				break;

			case svc_disconnect:
				Com_Error (ERR_DISCONNECT,"Server disconnected\n");
				break;

			case svc_reconnect:
				Com_Printf ("Server disconnected, reconnecting\n");

				if (cls.download) {
					/* close download */
					fclose (cls.download);
					cls.download = NULL;
				}

				cls.state = ca_connecting;
				cls.connect_time = -99999; /* CL_CheckForResend() will fire immediately */
				break;

			case svc_print:
				i = MSG_ReadByte (&net_message);

				if (i == PRINT_CHAT) {
					S_StartLocalSound ("misc/talk.wav");
					con.ormask = 128;
				}

				Com_Printf ("%s", MSG_ReadString (&net_message));
				con.ormask = 0;
				break;

			case svc_centerprint:
				SCR_CenterPrint (MSG_ReadString (&net_message));
				break;

			case svc_stufftext:
				s = MSG_ReadString (&net_message);
				Com_DPrintf ("stufftext: %s\n", s);
				Cbuf_AddText (s);
				break;

			case svc_serverdata:
				Cbuf_Execute (); /* make sure any stuffed commands are done */
				CL_ParseServerData ();
				break;

			case svc_configstring:
				CL_ParseConfigString ();
				break;

			case svc_sound:
				CL_ParseStartSoundPacket();
				break;

			case svc_spawnbaseline:
				CL_ParseBaseline ();
				break;

			case svc_temp_entity:
				CL_ParseTEnt ();
				break;

			case svc_muzzleflash:
				CL_AddMuzzleFlash ();
				break;

			case svc_muzzleflash2:
				CL_AddMuzzleFlash2 ();
				break;

			case svc_download:
				CL_ParseDownload ();
				break;

			case svc_frame:
				CL_ParseFrame ();
				break;

			case svc_inventory:
				CL_ParseInventory ();
				break;

			case svc_layout:
				s = MSG_ReadString (&net_message);
				strncpy (cl.layout, s, sizeof(cl.layout)-1);
				break;

			case svc_playerinfo:
			case svc_packetentities:
			case svc_deltapacketentities:
				Com_Error (ERR_DROP, "Out of place frame data");
				break;
		}
	}

	CL_AddNetgraph ();

	/* we don't know if it is ok to save a demo message until
	   after we have parsed the frame */
	if (cls.demorecording && !cls.demowaiting)
		CL_WriteDemoMessage ();

}

